#include <modules\graphics\mesh.h>

namespace DelGE
{
	namespace Modules
	{
		namespace Graphics
		{
			Mesh::Mesh()
			{
				this->pfVertexArray = NULL;
				this->pfTextureCoordArray = NULL;
				this->pfColorArray = NULL;
				this->pfNormalArray = NULL;
				this->piIndexArray = NULL;

				this->bNeedsRebuilding = true;
			}

			Mesh::Mesh(Mesh *pMesh)
			{
				vector<Vertex*> meshVertexList = pMesh->GetVertices();
				vector<IndexedTriangle*> meshIndexedTriangleList = pMesh->GetIndexedTriangles();

				for (int iVertexIndex = 0; iVertexIndex < pMesh->GetVertexCount(); iVertexIndex ++)
				{
					Vertex *pVertex = pMesh->GetVertex(iVertexIndex);
					this->AddVertex(pVertex->position[0], pVertex->position[1], pVertex->position[2],
									pVertex->textureUV[0], pVertex->textureUV[1],
									Color(pVertex->color[0], pVertex->color[1], pVertex->color[2], pVertex->color[3]),
									pVertex->normal[0], pVertex->normal[1], pVertex->normal[2]);
				}

				for (int iIndexedTriangleIndex = 0; iIndexedTriangleIndex < pMesh->GetIndexedTriangleCount(); iIndexedTriangleIndex ++)
				{
					IndexedTriangle *pIndexedTriangle = pMesh->GetIndexedTriangle(iIndexedTriangleIndex);
					this->AddIndexedTriangle(pIndexedTriangle->indices[0], pIndexedTriangle->indices[1], pIndexedTriangle->indices[2]);
				}

				this->pfVertexArray =			(float*)malloc((sizeof(float) * 3) * pMesh->GetVertexCount());
				this->pfTextureCoordArray =		(float*)malloc((sizeof(float) * 2) * pMesh->GetVertexCount());
				this->pfColorArray =			(float*)malloc((sizeof(float) * 4) * pMesh->GetVertexCount());
				this->pfNormalArray =			(float*)malloc((sizeof(float) * 3) * pMesh->GetVertexCount());
				this->piIndexArray =			(unsigned int*)malloc((sizeof(unsigned int) * 3) * pMesh->GetIndexedTriangleCount());

				float *pMeshVertexArray				= pMesh->GetVertexArray();
				float *pMeshTextureCoordArray		= pMesh->GetTextureCoordArray();
				float *pMeshColorArray				= pMesh->GetColorArray();
				float *pMeshNormalArray				= pMesh->GetNormalArray();
				unsigned int *pMeshIndexArray		= pMesh->GetIndexArray();

				memcpy(this->pfVertexArray,			pMeshVertexArray, 
					  ((sizeof(float) * 3) * pMesh->GetVertexCount()));

				memcpy(this->pfTextureCoordArray,	pMeshTextureCoordArray, 
					  ((sizeof(float) * 2) * pMesh->GetVertexCount()));

				memcpy(this->pfColorArray,			pMeshColorArray, 
					  ((sizeof(float) * 4) * pMesh->GetVertexCount()));

				memcpy(this->pfNormalArray,			pMeshNormalArray, 
					  ((sizeof(float) * 3) * pMesh->GetVertexCount()));

				memcpy(this->piIndexArray,			pMeshIndexArray, 
					  ((sizeof(unsigned int) * 3) * pMesh->GetIndexedTriangleCount()));

				this->bNeedsRebuilding = false;
			}

			Mesh::~Mesh()
			{
				unsigned int i = 0;

				for (i = 0; i < this->vVertexList.size(); i++)
					delete this->vVertexList[i];

				for (i = 0; i < this->vIndexedTriangleList.size(); i++)
					delete this->vIndexedTriangleList[i];

				this->vVertexList.clear();
				this->vIndexedTriangleList.clear();

				if (this->pfVertexArray != NULL) delete [] this->pfVertexArray; this->pfVertexArray = NULL;
				if (this->pfTextureCoordArray != NULL) delete [] this->pfTextureCoordArray; this->pfTextureCoordArray = NULL;
				if (this->pfColorArray != NULL) delete [] this->pfColorArray; this->pfColorArray = NULL;
				if (this->pfNormalArray != NULL) delete [] this->pfNormalArray; this->pfNormalArray = NULL;
				if (this->piIndexArray != NULL) delete [] this->piIndexArray; this->piIndexArray = NULL;

				this->bNeedsRebuilding = false; // It's dead Jim.
			}

			Mesh* Mesh::CreateCube(float fWidth, float fHeight, float fDepth, float fXOffset, float fYOffset, float fZOffset)
			{
				Mesh *pMesh = new Mesh();

				// Top
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			0, 0,	Color(255, 255, 255, 255),  0, 1, 0);		// 00
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			1, 0,	Color(255, 255, 255, 255),  0, 1, 0);		// 01
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		1, 1,	Color(255, 255, 255, 255),  0, 1, 0);		// 02
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		0, 1,	Color(255, 255, 255, 255),  0, 1, 0);		// 03

				// Bottom
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				0, 0,	Color(255, 255, 255, 255),  0, -1, 0);		// 04
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				1, 0,	Color(255, 255, 255, 255),  0, -1, 0);		// 05
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			1, 1,	Color(255, 255, 255, 255),  0, -1, 0);		// 06
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			0, 1,	Color(255, 255, 255, 255),  0, -1, 0);		// 07

				// Back
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			0, 0,	Color(255, 255, 255, 255),  0, 0, 1);		// 08
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			1, 0,	Color(255, 255, 255, 255),  0, 0, 1);		// 09
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				0, 1,	Color(255, 255, 255, 255),  0, 0, 1);		// 10
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				1, 1,	Color(255, 255, 255, 255),  0, 0, 1);		// 11

				// Front
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		0, 0,	Color(255, 255, 255, 255),  0, 0, -1);		// 12
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		1, 0,	Color(255, 255, 255, 255),  0, 0, -1);		// 13
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			0, 1,	Color(255, 255, 255, 255),  0, 0, -1);		// 14
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			1, 1,	Color(255, 255, 255, 255),  0, 0, -1);		// 15

				// Left
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			0, 0,	Color(255, 255, 255, 255),  1, 0, 0);		// 16
				pMesh->AddVertex(0 + (fWidth * fXOffset),		fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		1, 0,	Color(255, 255, 255, 255),  1, 0, 0);		// 17
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				0, 1,	Color(255, 255, 255, 255),  1, 0, 0);		// 18
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			1, 1,	Color(255, 255, 255, 255),  1, 0, 0);		// 19

				// Right
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	0 + (fDepth * fZOffset),			0, 0,	Color(255, 255, 255, 255),  -1, 0, 0);		// 20
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	fHeight + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),		1, 0,	Color(255, 255, 255, 255),  -1, 0, 0);		// 21
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	0 + (fDepth * fZOffset),				0, 1,	Color(255, 255, 255, 255),  -1, 0, 0);		// 22
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0 + (fHeight * fYOffset),	fDepth + (fDepth * fZOffset),			1, 1,	Color(255, 255, 255, 255),  -1, 0, 0);		// 23

				// Top
				pMesh->AddIndexedTriangle(0, 1, 2);
				pMesh->AddIndexedTriangle(0, 2, 3);

				// Bottom
				pMesh->AddIndexedTriangle(6, 5, 4);
				pMesh->AddIndexedTriangle(4, 7, 6);

				// Front
				pMesh->AddIndexedTriangle(12, 13, 14);
				pMesh->AddIndexedTriangle(13, 15, 14);

				// Back
				pMesh->AddIndexedTriangle(10, 9, 8);
				pMesh->AddIndexedTriangle(10, 11, 9);

				// Left
				pMesh->AddIndexedTriangle(16, 17, 18);
				pMesh->AddIndexedTriangle(17, 19, 18);

				// Right
				pMesh->AddIndexedTriangle(22, 21, 20);
				pMesh->AddIndexedTriangle(22, 23, 21);

				pMesh->BuildMesh();

				return pMesh;
			}

			Mesh* Mesh::CreatePlane(float fWidth, float fDepth, float fXOffset, float fZOffset)
			{
				Mesh *pMesh = new Mesh();

				// Top
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0,	0 + (fDepth * fZOffset)			, 0, 0);	// 0
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0,	0 + (fDepth * fZOffset)			, 1, 0);	// 1
				pMesh->AddVertex(fWidth + (fWidth * fXOffset),	0,	fDepth + (fDepth * fZOffset)	, 1, 1);	// 2
				pMesh->AddVertex(0 + (fWidth * fXOffset),		0,	fDepth + (fDepth * fZOffset)	, 0, 1);	// 3

				pMesh->AddIndexedTriangle(0, 1, 2);
				pMesh->AddIndexedTriangle(0, 2, 3);

				pMesh->BuildMesh();

				return pMesh;
			}

			void Mesh::AddVertex(float fX, float fY, float fZ, float fU, float fV, const Color& rColor, float fNormalX, float fNormalY, float fNormalZ)
			{
				Vertex *pVertex = new Vertex(fX, fY, fZ, fU, fV);
				pVertex->color = rColor;
				pVertex->normal[0] = fNormalX;
				pVertex->normal[1] = fNormalY;
				pVertex->normal[2] = fNormalZ;
				this->vVertexList.push_back(pVertex);

				this->bNeedsRebuilding = true;
			}
			
			void Mesh::AddIndexedTriangle(int iIndex0, int iIndex1, int iIndex2)
			{
				IndexedTriangle *pIndexedTriangle = new IndexedTriangle(iIndex0, iIndex1, iIndex2);
				this->vIndexedTriangleList.push_back(pIndexedTriangle);

				this->bNeedsRebuilding = true;
			}

			void Mesh::RemoveVertex(int iIndex)
			{
				delete this->vVertexList[iIndex];

				this->vVertexList.erase(this->vVertexList.begin() + iIndex);

				// We must remove triangles that use this vertext index also
				vector<IndexedTriangle* > closedList = vector<IndexedTriangle *>(this->vIndexedTriangleList); // copy triangles to a closed list.
				for (unsigned int i = 0; i < closedList.size(); i ++)
				{
					for (unsigned int j = 0; j < 3; j ++)
					{
						if (closedList[i]->indices[j] == iIndex)
						{
							delete closedList[i];

							this->vIndexedTriangleList.erase(this->vIndexedTriangleList.begin() + i);

							break;
						}
					}
				}

				this->bNeedsRebuilding = true;
			}

			void Mesh::RemoveIndexedTriangle(int iIndex)
			{
				delete this->vIndexedTriangleList[iIndex];

				this->vIndexedTriangleList.erase(this->vIndexedTriangleList.begin() + iIndex);

				this->bNeedsRebuilding = true;
			}

			void Mesh::ClearMesh()
			{
				unsigned int i = 0;

				for (i = 0; i < this->vVertexList.size(); i++)
					delete this->vVertexList[i];

				for (i = 0; i < this->vIndexedTriangleList.size(); i++)
					delete this->vIndexedTriangleList[i];

				this->vVertexList.clear();
				this->vIndexedTriangleList.clear();

				if (this->pfVertexArray != NULL) delete [] this->pfVertexArray; this->pfVertexArray = NULL;
				if (this->pfTextureCoordArray != NULL) delete [] this->pfTextureCoordArray; this->pfTextureCoordArray = NULL;
				if (this->pfColorArray != NULL) delete [] this->pfColorArray; this->pfColorArray = NULL;
				if (this->pfNormalArray != NULL) delete [] this->pfNormalArray; this->pfNormalArray = NULL;
				if (this->piIndexArray != NULL) delete [] this->piIndexArray; this->piIndexArray = NULL;

				this->bNeedsRebuilding = true;
			}

			void Mesh::BuildMesh()
			{
				unsigned int i;

				if (this->pfVertexArray != NULL) delete [] this->pfVertexArray; this->pfVertexArray = NULL;
				if (this->pfTextureCoordArray != NULL) delete [] this->pfTextureCoordArray; this->pfTextureCoordArray = NULL;
				if (this->pfColorArray != NULL) delete [] this->pfColorArray; this->pfColorArray = NULL;
				if (this->pfNormalArray != NULL) delete [] this->pfNormalArray; this->pfNormalArray = NULL;
				if (this->piIndexArray != NULL) delete [] this->piIndexArray; this->piIndexArray = NULL;
				
				this->piIndexArray = new unsigned int[this->vIndexedTriangleList.size() * 3];
				this->pfVertexArray = new float[this->vVertexList.size() * 3];
				this->pfTextureCoordArray = new float[this->vVertexList.size() * 2];
				this->pfNormalArray = new float[this->vVertexList.size() * 3];
				this->pfColorArray = new float[this->vVertexList.size() * 4];

				for (i = 0; i < this->vVertexList.size(); ++ i)
				{
					if (this->vVertexList[i] != NULL)
					{
						this->pfVertexArray[(i * 3) + 0] = this->vVertexList[i]->position[0];
						this->pfVertexArray[(i * 3) + 1] = this->vVertexList[i]->position[1];
						this->pfVertexArray[(i * 3) + 2] = this->vVertexList[i]->position[2];

						this->pfTextureCoordArray[(i * 2) + 0] = this->vVertexList[i]->textureUV[0];
						this->pfTextureCoordArray[(i * 2) + 1] = this->vVertexList[i]->textureUV[1];

						this->pfNormalArray[(i * 3) + 0] = this->vVertexList[i]->normal[0];
						this->pfNormalArray[(i * 3) + 1] = this->vVertexList[i]->normal[1];
						this->pfNormalArray[(i * 3) + 2] = this->vVertexList[i]->normal[2];

						this->pfColorArray[(i * 4) + 0] = this->vVertexList[i]->color.GetRedScalar();
						this->pfColorArray[(i * 4) + 1] = this->vVertexList[i]->color.GetGreenScalar();
						this->pfColorArray[(i * 4) + 2] = this->vVertexList[i]->color.GetBlueScalar();
						this->pfColorArray[(i * 4) + 3] = this->vVertexList[i]->color.GetAlphaScalar();
					}
					else
					{
						this->pfVertexArray[(i * 3) + 0] = 0;
						this->pfVertexArray[(i * 3) + 1] = 0;
						this->pfVertexArray[(i * 3) + 2] = 0;

						this->pfTextureCoordArray[(i * 2) + 0] = 0;
						this->pfTextureCoordArray[(i * 2) + 1] = 0;

						this->pfNormalArray[(i * 3) + 0] = 0;
						this->pfNormalArray[(i * 3) + 1] = 0;
						this->pfNormalArray[(i * 3) + 2] = 0;

						this->pfColorArray[(i * 4) + 0] = 0;
						this->pfColorArray[(i * 4) + 1] = 0;
						this->pfColorArray[(i * 4) + 2] = 0;
						this->pfColorArray[(i * 4) + 3] = 0;
					}
				}

				for (i = 0; i < this->vIndexedTriangleList.size(); ++ i)
				{
					this->piIndexArray[(i * 3) + 0] = this->vIndexedTriangleList[i]->indices[0];
					this->piIndexArray[(i * 3) + 1] = this->vIndexedTriangleList[i]->indices[1];
					this->piIndexArray[(i * 3) + 2] = this->vIndexedTriangleList[i]->indices[2];
				}

				this->bNeedsRebuilding = false;
			}

			void Mesh::UpdateVertexPositions()
			{
				if (this->pfVertexArray == NULL)
				{
					this->BuildMesh();
				}
				else
				{
					unsigned int i;

					for (i = 0; i < this->vVertexList.size(); ++ i)
					{
						if (this->vVertexList[i] != NULL)
						{
							this->pfVertexArray[(i * 3) + 0] = this->vVertexList[i]->position[0];
							this->pfVertexArray[(i * 3) + 1] = this->vVertexList[i]->position[1];
							this->pfVertexArray[(i * 3) + 2] = this->vVertexList[i]->position[2];
						}
						else
						{
							this->pfVertexArray[(i * 3) + 0] = 0;
							this->pfVertexArray[(i * 3) + 1] = 0;
							this->pfVertexArray[(i * 3) + 2] = 0;
						}
					}

					this->bNeedsRebuilding = false;
				}
			}

			Vertex* Mesh::GetVertex(unsigned int iIndex)
			{
				if (this->vVertexList.size() > 0 && iIndex < this->vVertexList.size())
					return this->vVertexList[iIndex];

				return NULL;
			}

			IndexedTriangle* Mesh::GetIndexedTriangle(unsigned int iIndex)
			{
				if (this->vIndexedTriangleList.size() > 0 && iIndex < this->vIndexedTriangleList.size())
					return this->vIndexedTriangleList[iIndex];

				return NULL;
			}

			float* Mesh::GetVertexArray()
			{
				return this->pfVertexArray;
			}
			
			float* Mesh::GetTextureCoordArray()
			{
				return this->pfTextureCoordArray;
			}

			float* Mesh::GetNormalArray()
			{
				return this->pfNormalArray;
			}

			float* Mesh::GetColorArray()
			{
				return this->pfColorArray;
			}
			
			unsigned int* Mesh::GetIndexArray()
			{
				return this->piIndexArray;
			}

			int Mesh::GetVertexCount()
			{
				return this->vVertexList.size();
			}

			int Mesh::GetIndexedTriangleCount()
			{
				return this->vIndexedTriangleList.size();
			}

			vector<Vertex*>& Mesh::GetVertices()
			{
				return this->vVertexList;
			}

			vector<IndexedTriangle*>& Mesh::GetIndexedTriangles()
			{
				return this->vIndexedTriangleList;
			}

			bool Mesh::NeedsRebuilding()
			{
				return this->bNeedsRebuilding;
			}

			void Mesh::Rotate(float fPitch, float fYaw, float fRoll)
			{
				Matrix mTransformation = Matrix::CreateRotationYaw(fYaw) * 
										 Matrix::CreateRotationPitch(fPitch) * 
										 Matrix::CreateRotationRoll(fRoll);

				for (unsigned int i = 0; i < this->vVertexList.size(); i ++)
				{
					Vector3 vVertexPosition = Vector3(this->vVertexList[i]->position[0],
													  this->vVertexList[i]->position[1],
													  this->vVertexList[i]->position[2]);
					
					vVertexPosition = vVertexPosition * mTransformation;
					
					this->vVertexList[i]->position[0] = vVertexPosition[0];
					this->vVertexList[i]->position[1] = vVertexPosition[1];
					this->vVertexList[i]->position[2] = vVertexPosition[2];
				}

				this->bNeedsRebuilding = true;
			}

			void Mesh::Translate(float fX, float fY, float fZ)
			{
				Matrix mTransformation = Matrix::Create3DTranslation(fX, fY, fZ);

				for (unsigned int i = 0; i < this->vVertexList.size(); i ++)
				{
					Vector3 vVertexPosition = Vector3(this->vVertexList[i]->position[0],
													  this->vVertexList[i]->position[1],
													  this->vVertexList[i]->position[2]);
					
					vVertexPosition = vVertexPosition * mTransformation;
					
					this->vVertexList[i]->position[0] = vVertexPosition[0];
					this->vVertexList[i]->position[1] = vVertexPosition[1];
					this->vVertexList[i]->position[2] = vVertexPosition[2];
				}

				this->bNeedsRebuilding = true;
			}

			void Mesh::Scale(float fScaleX, float fScaleY, float fScaleZ)
			{
				Matrix mTransformation = Matrix::Create3DScale(fScaleX, fScaleY, fScaleZ);

				for (unsigned int i = 0; i < this->vVertexList.size(); i ++)
				{
					Vector3 vVertexPosition = Vector3(this->vVertexList[i]->position[0],
													  this->vVertexList[i]->position[1],
													  this->vVertexList[i]->position[2]);
					
					vVertexPosition = vVertexPosition * mTransformation;
					
					this->vVertexList[i]->position[0] = vVertexPosition[0];
					this->vVertexList[i]->position[1] = vVertexPosition[1];
					this->vVertexList[i]->position[2] = vVertexPosition[2];
				}

				this->bNeedsRebuilding = true;
			}
		}
	}
}